package com.threeti.mecool.core.domain.model.plan.pojo;

import com.threeti.mecool.core.application.message.MessageService;
import com.threeti.mecool.core.application.message.SystemMessageService;
import com.threeti.mecool.core.domain.model.acl.PermissionConst;
import com.threeti.mecool.core.domain.model.acl.User;
import com.threeti.mecool.core.domain.model.message.MobileMessage;
import com.threeti.mecool.core.domain.model.plan.PlanJobResult;
import com.threeti.mecool.core.domain.model.plan.PlanResult;
import com.threeti.mecool.core.exception.MessageException;
import com.threeti.mecool.core.infrastructure.persistence.acl.UserRepository;
import com.threeti.mecool.core.infrastructure.persistence.plan.PlanJobResultRepository;
import com.threeti.mecool.core.infrastructure.persistence.plan.PlanRepository;
import com.threeti.mecool.core.infrastructure.persistence.plan.PlanSpecs;
import com.threeti.mecool.core.util.MyAclPermissionEvaluator;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.annotate.JsonManagedReference;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.domain.Specifications;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;

@Configurable
@Entity
@Table(name = "DM_PLAN",
        uniqueConstraints = @UniqueConstraint(columnNames = {"userLoginName", "planDate", "type"}))
public class Plan {
    public static final String STATUS_NEW = "new";
    public static final String STATUS_CONFIRM = "confirm";
    public static final String STATUS_DENY = "deny";
    public static final String STATUS_CHANGE = "change";
    /**
     * 指派任务 *
     */
    public static final String TYPE_ASSIGNMENT = "assignment";

    private Long id;
    private Integer version;
    private String userLoginName;
    private String name;
    private Date planDate;
    private List<PlanProject> planProjects = new ArrayList<PlanProject>();
    private String status = STATUS_NEW;
    private String denyNotes;
    private String changeNotes;
    private String assignedBy;
    private String type;
    private String confirmedBy;

    private PlanRepository planRepository;
    private PlanJobResultRepository planJobResultRepository;
    private MessageService messageService;
    private SystemMessageService systemMessageService;
    private UserRepository userRepository;
    private PermissionEvaluator permissionEvaluator;

    //<editor-fold desc="static finder">
    public static Plan findById(Long id) {
        if (id == null) {
            return null;
        } else {
            return planRepository().findOne(id);
        }
    }

    public static Plan findUserDailyPlan(String userLoginName, Date planDate) {
        return planRepository().findByUserLoginNameAndPlanDateAndTypeIsNull(userLoginName, planDate);
    }

    public static Plan findUserAssignedPlan(String userLoginName, Date planDate) {
        return planRepository().findByUserLoginNameAndPlanDateAndType(userLoginName, planDate, Plan.TYPE_ASSIGNMENT);
    }

    public static Page<Plan> findBySpec(Specification<Plan> spec, Pageable pageable) {
        return planRepository().findAll(spec, pageable);
    }

    public static Plan optAssignedPlanBy(String userLoginName, String projectCode, String storeCode,
                                         Long jobId, Date planDate, Date startTime, Date endTime) {
        Plan plan = planRepository().findByUserLoginNameAndPlanDateAndType(userLoginName, planDate, Plan.TYPE_ASSIGNMENT);
        if (plan == null) {
            plan = new Plan();
            plan.setPlanDate(planDate);
            plan.setUserLoginName(userLoginName);
            plan.setName(userLoginName+"-"+new SimpleDateFormat("yyyyMMdd").format(planDate)+"-S");
            plan.setType(Plan.TYPE_ASSIGNMENT);
            plan.setStatus(Plan.STATUS_CONFIRM);
        }
        PlanProject planProject = plan.optPlanProjectByProjectCode(projectCode);
        PlanJob planJob = planProject.optPlanJobByJobIdAndStoreCode(jobId, storeCode);
        planJob.setStartTime(startTime);
        planJob.setEndTime(endTime);
        return plan;
    }

    public static Plan optAssignedPlanBy(String userLoginName, Date planDate) {
        Plan plan = planRepository().findByUserLoginNameAndPlanDateAndType(userLoginName, planDate, Plan.TYPE_ASSIGNMENT);
        if (plan == null) {
            plan = new Plan();
            plan.setPlanDate(planDate);
            plan.setUserLoginName(userLoginName);
            plan.setName(userLoginName+"-"+new SimpleDateFormat("yyyyMMdd").format(planDate)+"-S");
            plan.setType(Plan.TYPE_ASSIGNMENT);
            plan.setStatus(Plan.STATUS_CONFIRM);
        }
        return plan;
    }

    public static Page<Plan> findByUserLoginName(String userLoginName, Pageable pageable) {
        return planRepository().findByUserLoginName(userLoginName, pageable);
    }
    //</editor-fold>

    //<editor-fold desc="helper getter">
    @Transient
    @JsonIgnore
    public String getAssignedByName() {
        if (assignedBy == null || assignedBy.isEmpty()) return "";
        User user = userRepository.findByloginName(assignedBy);
        return user != null ? user.getDisplayName() : "";
    }

    @Transient
    @JsonIgnore
    public String getConfirmedByName() {
        if (confirmedBy == null || confirmedBy.isEmpty()) return "";
        User user = userRepository.findByloginName(confirmedBy);
        return user != null ? user.getDisplayName() : "";
    }

    @Transient
    @JsonIgnore
    public String getUserDisplayName() {
        User user = userRepository.findByloginName(userLoginName);
        return user != null ? user.getDisplayName() : "";
    }
    //</editor-fold>

    public void addPlanProject(PlanProject planProject) {
        planProject.setPlan(this);
        planProjects.add(planProject);
    }

    public PlanProject optPlanProjectByProjectCode(String projectCode) {
        for (PlanProject planProject : planProjects) {
            if (projectCode.equals(planProject.getProjectCode())) {
                return planProject;
            }
        }
        PlanProject planProject = new PlanProject();
        planProject.setProjectCode(projectCode);
        addPlanProject(planProject);
        return planProject;
    }

    public void clearPersistStatus() {
        id = null;
        version = null;
        for (PlanProject planProject : planProjects) {
            planProject.setId(null);
            planProject.setVersion(null);
            for (PlanJob planJob : planProject.getPlanJobs()) {
                planJob.setId(null);
                planJob.setVersion(null);
            }
        }
    }

    @Transactional
    public void delete() {
        planRepository.delete(this);
    }

//    @PrePersist
//    @PreUpdate
    private void validate() {
        validateNoFinishedJob();
        validateParallelSOJob();
        validateFinishedSOJob();
    }

    private void validateNoFinishedJob() {
        if (id != null && !TYPE_ASSIGNMENT.equals(type)) {
            List<PlanJobResult> list = planJobResultRepository.findByPlanJob_PlanProject_Plan_IdAndFinishedAtIsNotNullOrJobStatusFinished(id);
            if (list.size() > 0) {
                throw new RuntimeException("计划已经开始执行，不允许修改");
            }
        }
    }

    private void validateParallelSOJob() {
        Map<String, Set<String>> map = new HashMap<String, Set<String>>();
        for (PlanProject planProject : planProjects) {
            for (PlanJob planJob : planProject.getPlanJobs()) {
                if (Job.CODE_JOB_STORE_OPENING.equals(planJob.getJob().getCode())) {
                    Set<String> storeCodes = map.get(planProject.getProjectCode());
                    if (storeCodes == null) {
                        storeCodes = new HashSet<String>();
                        map.put(planProject.getProjectCode(), storeCodes);
                    }
                    if (!storeCodes.contains(planJob.getStoreCode())) {
                        storeCodes.add(planJob.getStoreCode());
                    } else {
                        throw new RuntimeException(String.format("同一项目的同一终端不允许添加多个开店任务，请检查计划配置\n项目 - [%s]\n门店 - [%s]",
                                planJob.getPlanProject().getProjectName(), planJob.getStoreName()));
                    }
                }
            }
        }
    }

    private void validateFinishedSOJob() {
        for (PlanProject planProject : planProjects) {
            for (PlanJob planJob : planProject.getPlanJobs()) {
                Job job = planJob.getJob();
                if (Job.CODE_JOB_STORE_OPENING.equals(job.getCode())) {
                    List<PlanJobResult> planJobResults = planJobResultRepository.findByPlanJob_Job_CodeAndProjectCodeAndStoreCodeAndJobStatus(
                            job.getCode(), planProject.getProjectCode(), planJob.getStoreCode(), PlanJobResult.JOB_STATUS_FINISHED);
                    boolean isUpdate = planJobResults.size() == 0;
                    for (PlanJobResult planJobResult : planJobResults) {
                        PlanJob tempPlanJob = planJobResult.getPlanJob();
                        if (tempPlanJob != null && tempPlanJob.getId().equals(planJob.getId())) {
                            isUpdate = true;
                            break;
                        }
                    }
                    if (!isUpdate) {
                        throw new RuntimeException(String.format("同一项目的同一终端已经存在成功完成的开店任务，不允许重复添加，请检查计划配置\n项目 - [%s]\n门店 - [%s]",
                                planJob.getPlanProject().getProjectName(), planJob.getStoreName()));
                    }
                }
            }
        }
    }

    @Transactional
    public void save() {
        validate();
        boolean isPlanNeedAudit = ((MyAclPermissionEvaluator) permissionEvaluator).hasPermission(
                userLoginName, PermissionConst.MODULE_SUB_PLAN_NEEDTOBE_AUDITED[0], PermissionConst.MODULE_SUB_PLAN_NEEDTOBE_AUDITED[1], PermissionConst.P_ADMIN_STR);
        if (!isPlanNeedAudit) {
            status = STATUS_CONFIRM;
        }
        MobileMessage mobileMessage = new MobileMessage();
        mobileMessage.setReceiver(userLoginName);
        mobileMessage.setCreator(userLoginName);
        mobileMessage.setHead("工作计划");
        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        boolean sendMessage = false;
        if (!Plan.STATUS_NEW.equals(status)) {
//            Plan originPlan = Plan.findById(id);
//            if (!originPlan.getStatus().equals(status)) {
                sendMessage = true;
                if (Plan.STATUS_CONFIRM.equals(status)) {
                    mobileMessage.setBody("您有新的工作计划，计划日期：" + dateFormat.format(planDate));
                } else {
                    mobileMessage.setBody("您的工作计划有变动，计划日期：" + dateFormat.format(planDate));
                }
//            }
        }
        if (Plan.TYPE_ASSIGNMENT.equals(type)) {
            if (userLoginName != null && !userLoginName.isEmpty()) {
                mobileMessage.setBody("您有新的紧急任务，任务日期：" + dateFormat.format(planDate));
                mobileMessage.setPriority(MobileMessage.Priority.ASSIGN);
                mobileMessage.setCreator(assignedBy);
                sendMessage = true;
            }
        }
        planRepository.save(this);
        if (Plan.STATUS_CONFIRM.equals(status)) {
            PlanResult planResult = PlanResult.optByPlanId(id);
            //<editor-fold desc="remove unfinished job result">
//            planResult.getPlanJobResults().clear();
            List<PlanJobResult> outdatedJobResultList = new ArrayList<PlanJobResult>(planResult.getPlanJobResults().size());
            for (PlanJobResult planJobResult : planResult.getPlanJobResults()) {
                if (planJobResult.getFinishedAt() == null) {
                    outdatedJobResultList.add(planJobResult);
                }
            }
            for (int i = outdatedJobResultList.size() - 1; i >= 0; i--) {
//                planJobResultRepository.delete(outdatedJobResultList.get(i));
                planResult.getPlanJobResults().remove(outdatedJobResultList.get(i));
            }
            //</editor-fold>
            for (PlanProject planProject : planProjects) {
                for (PlanJob planJob : planProject.getPlanJobs()) {
                    planResult.optPlanJobResultByPlanJobId(planJob.getId());
                }
            }
            planResult.save();
        }
        if (sendMessage) {
            try {
                String creator = TYPE_ASSIGNMENT.equals(type) ? assignedBy : userLoginName;
                systemMessageService.send(creator, mobileMessage.getReceiver(), mobileMessage.getHead(), mobileMessage.getBody());
                messageService.send(mobileMessage);
            } catch (MessageException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private static PlanRepository planRepository() {
        return new Plan().planRepository;
    }

    //<editor-fold desc="get/set">
    @Resource
    public void setMessageService(MessageService messageService) {
        this.messageService = messageService;
    }

    @Resource
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Resource
    public void setSystemMessageService(SystemMessageService systemMessageService) {
        this.systemMessageService = systemMessageService;
    }

    @Resource
    public void setPlanRepository(PlanRepository planRepository) {
        this.planRepository = planRepository;
    }

    @Resource
    public void setPlanJobResultRepository(PlanJobResultRepository planJobResultRepository) {
        this.planJobResultRepository = planJobResultRepository;
    }

    @Resource
    public void setPermissionEvaluator(PermissionEvaluator permissionEvaluator) {
        this.permissionEvaluator = permissionEvaluator;
    }

    @Id
    @GeneratedValue
    public Long getId() {
        return id;
    }

    @Version
    public Integer getVersion() {
        return version;
    }

    @NotNull
    public String getUserLoginName() {
        return userLoginName;
    }

    @NotNull
    @Size(min = 1, max = 50)
    public String getName() {
        return name;
    }

    @Temporal(TemporalType.DATE)
    @NotNull
    public Date getPlanDate() {
        return planDate;
    }

    @JsonManagedReference
    @OneToMany(mappedBy = "plan", cascade = CascadeType.ALL, orphanRemoval = true)
    public List<PlanProject> getPlanProjects() {
        return planProjects;
    }

    @NotNull
    public String getStatus() {
        return status == null ? Plan.STATUS_NEW : status;
    }

    public String getChangeNotes() {
        return changeNotes;
    }

    public String getDenyNotes() {
        return denyNotes;
    }

    public String getAssignedBy() {
        return assignedBy;
    }

    public String getType() {
        return type;
    }

    public String getConfirmedBy() {
        return confirmedBy;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public void setVersion(Integer version) {
        this.version = version;
    }

    public void setUserLoginName(String userLoginName) {
        this.userLoginName = userLoginName;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setPlanDate(Date planDate) {
        this.planDate = planDate;
    }

    public void setPlanProjects(List<PlanProject> planProjects) {
        this.planProjects = planProjects;
    }

    public void setStatus(String status) {
        this.status = status.toLowerCase();
    }

    public void setDenyNotes(String denyNotes) {
        this.denyNotes = denyNotes;
    }

    public void setChangeNotes(String changeNotes) {
        this.changeNotes = changeNotes;
    }

    public void setAssignedBy(String assignedBy) {
        this.assignedBy = assignedBy;
    }

    public void setType(String type) {
        this.type = type;
    }

    public void setConfirmedBy(String confirmedBy) {
        this.confirmedBy = confirmedBy;
    }
//</editor-fold>

    public static List<Plan> findByUserAfterDate(String userLoginName, Date date) {
        Specifications<Plan> specs = Specifications
                .where(PlanSpecs.userLoginNameIs(userLoginName))
                .and(PlanSpecs.planDateGreaterThan(date));
        return planRepository().findAll(specs);
    }
}
